#include<iostream>
#include<string>
#include<unistd.h>
#include<vector>
//#include<cerrno>
#include<sys/wait.h>
#include<sys/types.h>
#include"Task.hpp"

using namespace std;

// void work(int rfd)
// {
//     while(true)
//     {
//         sleep(1);
//         int command=0;
//         int n=read(rfd,&command,sizeof(command));
//         if(n==sizeof(int))
//         {
//             cout<<"pid is :"<<getpid()<<" handler task "<<endl;
//             ExcuteTask(command);
//         }
//         else if(n==0)
//         {
//             cout<<"sub process: "<<getpid()<<" quit"<<endl;
//         }
//     }
// }

class Channel
{
public:
    Channel(int wfd,pid_t id ,const string &name):_wfd(wfd),_subprocessid(id),_name(name)
    {}
    
    int GetWfd(){return _wfd;}
    pid_t GetProcessId(){return _subprocessid;}
    string GetName(){return _name;}

    void CloseChannel()
    {
        close(_wfd);
    }

    void Wait()
    {
        pid_t rid =waitpid(_subprocessid,nullptr,0);
        if(rid>0)
        {
            cout<<"wait: "<<rid<<" success "<<endl;
        }
    }

    ~Channel()
    {}


private:
    int _wfd;
    pid_t _subprocessid;
    string _name;

};

//c/c++形参命名规范
//const & :输入型参数
//& :输入输出型参数
//* :输出型参数

void CreateChannelAndSub(int num,vector<Channel> *channels,task_t task)//task_t task:回调函数
{

    //BUG???--->后续多次创建子进程时会把父进程之前的写端管道也拷贝下来
    for(int i=0;i<num;i++)
    {
        //1.创建管道
        int pipefd[2]={0};
        int n =pipe(pipefd);
        if(n<0)exit(1);

        //2.创建子进程
        pid_t id=fork();

        if(id==0)
        {
            //解决上述BUG方法，将第二次开始创建的子进程的写端遍历关闭
            if(!channels->empty())
            {   
                //第二次之后，开始创建的管道
                for(auto &channel:*channels)channel.CloseChannel();
            }

            //child
            close(pipefd[1]);
            //work(pipefd[0]);

            dup2(pipefd[0],0);//将管道的读端重定向到标准输入
            //work();//此时子进程不再有管道概念，不需要从文件描述符里读了
            task();//使用回调函数执行work()

            exit(0);
        }

        //3.构建一个channel名称
        string channel_name = "Channel ->"+to_string(i);

        //父进程
        close(pipefd[0]);
        //a.子进程的pid      b.父进程关心的管道的w端
        channels->push_back(Channel(pipefd[1],id,channel_name));
    }
}

int NextChannel(int channelnum)
{
    static int next=0;
    int channel =next;
    next++;
    next%=channelnum;
    return channel;
}

void SendTaskCommand(Channel &channel,int taskcommand)
{
      write(channel.GetWfd(),&taskcommand,sizeof(taskcommand));
}

void CtrlProcess(vector<Channel> &channels,int times=5)
{
    while(times--)
    {
    sleep(1);
    //a.选择一个任务
    int taskcommand = SelectTask();
    //b.选择一个信道和进程
    int channel_index=NextChannel(channels.size());//.size()? --->vector中的类的个数
    //c.发送任务
    SendTaskCommand(channels[channel_index],taskcommand);

    cout<<"taskcommand: "<<taskcommand<<" channel: "<<channels[channel_index].GetName()
    << " subprocessid: "<< channels[channel_index].GetProcessId()<<endl;
    
    }
}


void CleanUpChannel(vector<Channel> &channels)
{
    //为什么此时就就会出现BUG呢???-->创建子进程时候会把父进程之前的写端管道也拷贝过去
    for(auto &channel:channels)//--->已解决BUG
    {
        channel.CloseChannel();
        channel.Wait();
    }

    // for(auto &channel:channels)
    // {
    //     channel.CloseChannel();
    // }
    // for(auto &channel:channels)
    // {
    //     channel.Wait();
    // }


    // int num = channels.size() -1;
    // while(num >= 0)
    // {
    //     channels[num].CloseChannel();
    //     channels[num--].Wait();
    // }
}


// ./processpool 5
int main(int argc , char *argv[])
{
    if(argc != 2)
    {
        cerr<<"Usage: "<<argv[0]<<" processnum"<<endl;
        return 1;
    }

    int num =stoi(argv[1]);//接受用户输入的参数，例如 ./processpool 5 -->argv[0]="./processpool",argv[1]="5"

    vector<Channel> channels;
    LoadTask();

    //1.创建信道和子进程
    CreateChannelAndSub(num,&channels,work);

    //2.控制子进程
    CtrlProcess(channels);

    //3.回收子进程 a.关闭所有的写端 b.回收子进程
    CleanUpChannel(channels);
    







    // //for test
    // for(auto &e:channels)
    // {
    //     cout<<"============================================================"<<endl;
    //     cout<<e.GetWfd()<<endl<<e.GetProcessId()<<endl<<e.GetName()<<endl;
    // }

    
}